`timescale 1 ns / 1 ps
module TestSpi;
    logic clk, rst_n;
    logic [7:0] mtx_data[6] = '{8'hff, 8'ha5, 8'h3c, 8'h5a, 8'h0f, 8'hf0};
    logic [7:0] mrx_data[6];
    logic [7:0] stx_data[6] = '{8'hff, 8'h33, 8'haa, 8'h55, 8'hff, 8'h00};
    logic [7:0] srx_data[6];
    logic start = '0, mread, sread, mvalid, svalid, mbusy, sbusy;
    logic [7:0] mtx_d, mrx_d, stx_d, srx_d;
    logic [23:0] ss_mask = '0, ss_n;
    logic [7:0] trans_len = '0;
    logic mmosi, mmosi_tri, smiso, smiso_tri;
    logic sclk0, mosi, miso;
    assign mosi = mmosi_tri? 'z : mmosi;
    assign miso = smiso_tri? 'z : smiso;

    always #5 clk=~clk;

    dac7881_drive #(5, 1) theMaster(clk, rst_n, start,  trans_len,
        mread, mtx_d, mbusy,
        sclk0, , mmosi, mmosi_tri, miso, ss_n);
    initial begin
       clk<=0;
        rst_n<=1;
        #50 rst_n <=0;
        #100 rst_n <=1;
        repeat(10) @(posedge clk);
        @(posedge clk) {start, ss_mask, trans_len} = {1'b1, 24'd4, 8'd0};
        @(posedge clk) {start, ss_mask, trans_len} = {1'b0, 24'd0, 8'd0};
        @(posedge clk);
        wait(~mbusy);
        @(posedge clk) {start, ss_mask, trans_len} = {1'b1, 24'd8, 8'd0};
        @(posedge clk) {start, ss_mask, trans_len} = {1'b0, 24'd0, 8'd0};
        @(posedge clk);
        wait(~mbusy);
        @(posedge clk) {start, ss_mask, trans_len} = {1'b1, 24'd8, 8'd3};
        @(posedge clk) {start, ss_mask, trans_len} = {1'b0, 24'd0, 8'd0};
        @(posedge clk);

    end
    logic [2:0] mtx_idx = '0, mrx_idx = '0, stx_idx = '0, srx_idx = '0;

    always_ff@(posedge clk) if(mread) mtx_d = mtx_data[mtx_idx++];
    always_ff@(posedge clk) if(sread) stx_d = stx_data[stx_idx++];
    always_ff@(posedge clk) if(mvalid) mrx_data[mrx_idx++] = mrx_d;
    always_ff@(posedge clk) if(svalid) srx_data[srx_idx++] = srx_d;
endmodule



module dac7881_drive #(
    parameter HBR_DIV = 5,    //10Msps@100MHz
    parameter CHPA = 1
)(
    input wire clk, rst_n, start,
    input wire [15:0] trans_len,
    output logic read,
    input wire [15:0] tx_data,
    output logic busy,
    output logic sclk0, sclk1, mosi, mosi_tri,
    input wire miso,
    output logic  cs
);
    // hbr_cnt & hbit_cnt
    logic hbr_co, hbit_co;
    logic [20:0] hbit_cnt; // 2 + 16 * 256 = 4098 -> 13bit
    logic [20:0] hbit_cnt_max;
    always_ff@(posedge clk) begin
        if(!rst_n) hbit_cnt_max <= '0;
        else if(start)
            hbit_cnt_max <= 13'd1 + ((13'(trans_len) + 13'd1) << 4);
    end
    Counter #(HBR_DIV) hbrCnt(clk, rst_n, busy, , hbr_co);
    CounterMax #(13) hbitCnt(
        clk, rst_n, hbr_co, hbit_cnt_max, hbit_cnt, hbit_co);
    // busy driven
    always_ff@(posedge clk) begin
        if(!rst_n) busy <= '0;
        else if(start) busy <= '1;
        else if(hbit_co) busy <= '0;
    end
    // tx_data & mosi
    assign read = (CHPA == 0)?
                      start | (hbit_cnt[3:0] == 4'd15
                      & hbr_co & hbit_cnt < hbit_cnt_max - 13'd16)
                    : hbit_cnt[3:0] == 4'd0 & hbr_co
                      & hbit_cnt < hbit_cnt_max - 16;
    logic read_dly;
    always_ff@(posedge clk) read_dly <= read;
    wire out_shift = (CHPA == 0)? hbit_cnt[0] == 1'd1 & hbr_co
                                : hbit_cnt[0] == 1'd0 & hbr_co;
    logic [15:0] mosi_shift_reg;
    always_ff@(posedge clk) begin
        if(!rst_n) mosi_shift_reg <= '0;
        else if(read_dly) mosi_shift_reg <= tx_data;
        else if(out_shift) mosi_shift_reg <= mosi_shift_reg >> 1;
    end
    assign mosi_tri = ~busy;
    always_ff@(posedge clk) mosi <= mosi_shift_reg[0];

    // sclk & cs

    always_ff@(posedge clk) begin
        if(!rst_n) begin sclk0 <= '0; sclk1 <= '1; end
        else if(hbit_cnt < hbit_cnt_max) begin
            sclk0 <= hbit_cnt[0];
            sclk1 <= ~hbit_cnt[0];
        end
    end
    always_ff@(posedge clk) begin
        if(!rst_n) cs <= '1;
        else if(busy && hbit_cnt < hbit_cnt_max)
            cs <= 0;
        else cs <= '1;
    end
endmodule

module Counter #(
    parameter M = 100
)(
    input wire clk, rst_n, en,
    output reg [$clog2(M) - 1 : 0] cnt,
    output wire co
);
    assign co = en & (cnt == M - 1);
    always@(posedge clk) begin
        if(!rst_n) cnt <= 1'b0;
        else if(en) begin
            if(cnt < M - 1) cnt <= cnt + 1'b1;
            else cnt <= 1'b0;
        end
    end
endmodule


module CounterMax #(
    parameter DW = 8
)(
    input wire clk, rst_n, en,
    input wire [DW - 1 : 0] max,
    output logic [DW - 1 : 0] cnt,
    output logic co
);
    assign co = en & (cnt == max);
    always_ff@(posedge clk) begin
        if(!rst_n) cnt <= '0;
        else if(en) begin
            if(cnt < max) cnt <= cnt + 1'b1;
            else cnt <= '0;
        end
    end
endmodule